﻿using FyCacheClient.Msg;
using FyCacheClient.Pool;
using FySystem.Extension;
using FySystem.IO;
using FySystem.Net.SimSocket;
using FySystem.Secure;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;

namespace FyCacheClient
{
    public class SCacheClient:IDisposable
    {
        /// <summary>
        /// 默认端口
        /// </summary>
        private const int DEFAULT_PORT = 21000;
        /// <summary>
        /// 经过MD5加密后的用户名和密码
        /// </summary>
        private byte[] _byteAuth = null;

        /// <summary>
        /// 该连接长期占有，直到Dispose的时候才放回连接池，用于订阅和发布
        /// </summary>
        private Connect _connect;

        /// <summary>
        /// 所有的订阅记录
        /// </summary>
        private Dictionary<string, Action<SubscribeMessage>> _subscribes = new Dictionary<string, Action<SubscribeMessage>>();

        /// <summary>
        /// IP
        /// </summary>
        public IPAddress IpAddress { get; set; }

        /// <summary>
        /// 端口
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// 缓存用户
        /// </summary>
        public string User { get; private set; } 
        /// <summary>
        /// 缓存密码
        /// </summary>
        public string Password { get; private set; }

        /// <summary>
        /// 默认缓存库
        /// </summary>
        public string DB { get; private set; } = "db0";

        /// <summary>
        /// 缓存默认索引
        /// </summary>
        public ushort DbIndex { get; private set; } = 0;

        /// <summary>
        /// 超时时间，单位s，默认15s
        /// </summary>
        public ushort Timeout { get; set; } = 15;

        /// <summary>
        /// 推送时候用，订阅端可以根据该字段判断是谁推送的
        /// </summary>
        public byte[] ClientId { get; private set; } = Guid.NewGuid().ToByteArray();

        /// <summary>
        /// 以本地IP和默认端口初始化客户端
        /// </summary>
        public SCacheClient(string username, string password)
        {
            try
            {
                Init(IPAddress.Parse("127.0.0.1"), DEFAULT_PORT, username, password);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 以指定IP和默认端口初始化客户端
        /// </summary>
        /// <param name="ipAddress"></param>
        public SCacheClient(IPAddress ipAddress, string username, string password)
        {
            try
            {
                Init(ipAddress, DEFAULT_PORT, username, password);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 以本地IP和指定端口初始化客户端
        /// </summary>
        /// <param name="port"></param>
        public SCacheClient (int port, string username, string password)
        {
            try
            {
                Init(IPAddress.Parse("127.0.0.1"), port, username, password);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 以指定的IP、端口、用户名、密码初始化客户端
        /// </summary>
        /// <param name="iPAddress"></param>
        /// <param name="port"></param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        public SCacheClient (IPAddress iPAddress,int port,string username,string password)
        {
            try
            {
                Init(iPAddress, port, username, password);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 初始化客户端
        /// </summary>
        /// <param name="ipAddress"></param>
        /// <param name="port"></param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        private void Init(IPAddress ipAddress, int port, string username, string password)
        {
            try
            {
                IpAddress = ipAddress;
                Port = port;

                User = username;
                Password = password;
                _byteAuth = MD5.MD5EncryptB($"{User}{Password}");

                //初始化订阅和发布的连接
                _connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                _connect.Client.OnReceive = OnSubscribeReceived;
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 切换数据库
        /// </summary>
        /// <param name="dbIndex"></param>
        /// <returns></returns>
        public void UseDb(ushort dbIndex)
        {
            try
            {
                if (dbIndex > 10)
                    throw new Exception("dbIndex只能从0到9");

                DB = $"db{dbIndex}";
                DbIndex = dbIndex;                
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 设置缓存数据，如果key已存在，则更新，如果key不存在，则更新
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <exception cref="NotImplementedException"></exception>
        /// <returns>-1设置失败，0原本不存在，新增，1原本存在，更新</returns>
        public int Set(string key, CSon data)
        {
            int res = -1;
            Connect connect = null;
            try
            {
                byte[] requestId = null;
                var pack = PackageCreater.PackSet(ref _byteAuth, key, DbIndex, data, ref requestId);

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                CommandExecuter command = new CommandExecuter();
                CommandResponse response = command.Execute(connect.Client, pack, Timeout);
                res = BitConverter.ToInt32(response.Data);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                ConnectPool.FreeConnect(connect);
            }
            return res;
        }

        /// <summary>
        /// 以datas中每一项CSon的key对应的值来批量设置缓存数据
        /// 经测试，批量的仅仅只是比单个快3倍左右
        /// </summary>
        /// <param name="csonKey">datas中CSon对象的key名称</param>
        /// <param name="datas">数据</param>
        /// <returns>设置成功了多少项</returns>
        public int BatchSet(string csonKey, List<CSon> datas)
        {
            int res = -1;
            Connect connect = null;
            try
            {
                if (datas.IsNullOrEmpty())
                    throw new Exception("datas为空~");

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                CommandExecuter command = new CommandExecuter();

                //每100条数据打一个包
                const int maxCount= 100;
                int startIndex = 0;
                int endIndex = 0;

                while (true)
                {
                    endIndex = endIndex + maxCount;
                    endIndex = endIndex >= datas.Count ? datas.Count - 1 : endIndex;

                    byte[] requestId = null;
                    var pack = PackageCreater.PackBatchSet(ref _byteAuth, DbIndex, csonKey, datas, startIndex, endIndex, ref requestId);

                    CommandResponse response = command.Execute(connect.Client, pack, Timeout);
                    res += BitConverter.ToInt32(response.Data);

                    if (endIndex >= datas.Count - 1)
                        break;

                    startIndex = endIndex + 1;
                }                
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                ConnectPool.FreeConnect(connect);
            }
            return res;
        }

        /// <summary>
        /// 异步设置，经测试，没比同步的效率高多少
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="callback"></param>
        public void BeginSet(string key,CSon data,Action<int> callback)
        {
            Connect connect = null;
            try
            {
                byte[] requestId = null;
                var pack = PackageCreater.PackSet(ref _byteAuth, key, DbIndex, data, ref requestId);

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                AsyncCommandExecuter.Execute(connect, pack, (conn, response) =>
                {
                    try
                    {
                        if (callback != null)
                        {
                            int res = BitConverter.ToInt32(response.Data);
                            callback.Invoke(res);
                        }
                    }
                    catch { }
                    finally
                    {
                        ConnectPool.FreeConnect(conn);
                    }
                });
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 暂时测试用，以后再开放
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="callback"></param>
        public void BeginSetB(string key, byte[] data, Action<int> callback)
        {
            Connect connect = null;
            try
            {
                byte[] requestId = null;
                var pack = PackageCreater.PackSet(ref _byteAuth, key, DbIndex, data, ref requestId);

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                AsyncCommandExecuter.Execute(connect, pack, (conn, response) =>
                {
                    try
                    {
                        if (callback != null)
                        {
                            int res = BitConverter.ToInt32(response.Data);
                            callback.Invoke(res);
                        }
                    }
                    catch { }
                    finally
                    {
                        ConnectPool.FreeConnect(conn);
                    }
                });
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 获取数据
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public CSon Get(string key)
        {
            CSon res = null;
            Connect connect = null;
            try
            {
                byte[] requestId = null;
                var pack = PackageCreater.PackGet(ref _byteAuth, key, DbIndex, ref requestId);

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                CommandExecuter command = new CommandExecuter();
                CommandResponse response = command.Execute(connect.Client, pack, Timeout);
                if (response.Data == null)
                    return res;

                int position = 0;
                res = CSon.Parse(response.Data.AsSpan(), ref position, response.Data.Length);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                ConnectPool.FreeConnect(connect);
            }
            return res;
        }

        /// <summary>
        /// 删除指定Key的缓存
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Delete(string key)
        {
            bool res = false;
            Connect connect = null;
            try
            {
                byte[] requestId = null;
                var pack = PackageCreater.PackDelete(ref _byteAuth, key, DbIndex, ref requestId);

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                CommandExecuter command = new CommandExecuter();
                CommandResponse response = command.Execute(connect.Client, pack, Timeout);
                res = response.Data[0] == 1 ? true : false;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                ConnectPool.FreeConnect(connect);
            }
            return res;
        }

        /// <summary>
        /// 判断键值是否存在
        /// </summary>
        /// <param name="key"></param>
        /// <exception cref="NotImplementedException"></exception>
        public bool IsExist(string key)
        {
            bool res = false;
            Connect connect = null;
            try
            {
                byte[] requestId = null;
                var pack = PackageCreater.PackIsExist(ref _byteAuth, key, DbIndex, ref requestId);

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                CommandExecuter command = new CommandExecuter();
                CommandResponse response = command.Execute(connect.Client, pack, Timeout);
                if (response.Data == null)
                    return res;

                res = response.Data[0] == 0x01 ? true : false;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                ConnectPool.FreeConnect(connect);
            }
            return res;
        }

        #region 订阅和推送
        /// <summary>
        /// 订阅，和mqtt一样，topic可以是单层通配符[+]，或者多层通配符[*]
        /// 如果对稳定性要求比较高的话慎用，不像mqtt一样断线了都能推
        /// </summary>
        /// <param name="topic"></param>
        /// <param name="receiveCallback"></param>
        public void Subscribe(string topic, Action<SubscribeMessage> receiveCallback)
        {
            try
            {
                lock (_subscribes)
                {
                    if (_subscribes.ContainsKey(topic))
                        throw new Exception("同名主题已订阅~");

                    _subscribes.Add(topic, receiveCallback);
                }

                //向服务器发送订阅通知
                var pack = PackageCreater.PackSubscribe(ref _byteAuth, topic);

                //这里用同步发送，确保服务端收到数据后再执行后面的逻辑
                _connect.Client.Send(pack, true);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 推送
        /// </summary>
        /// <param name="topic"></param>
        /// <param name="message"></param>
        /// <exception cref="NotImplementedException"></exception>
        public int Publish(string topic, string message)
        {
            try
            {
                return Publish(topic, Encoding.UTF8.GetBytes(message));
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 推送
        /// </summary>
        /// <param name="topic"></param>
        /// <param name="message"></param>
        /// <returns>推送了多少个客户端，不确认是否推送成功，要确认的话可能会比较耗时</returns>
        public int Publish(string topic, byte[] message)
        {
            int res = -1;
            Connect connect = null;
            try
            {
                byte[] requestId = null;
                var pack = PackageCreater.PackPublish(ref _byteAuth, topic, ClientId, message, ref requestId);

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                CommandExecuter command = new CommandExecuter();
                CommandResponse response = command.Execute(connect.Client, pack, Timeout);
                if (response.Data == null)
                    return res;

                res = BitConverter.ToInt32(response.Data);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                ConnectPool.FreeConnect(connect);
            }
            return res;
        }

        /// <summary>
        /// 取消订阅
        /// </summary>
        /// <param name="topic"></param>
        /// <returns></returns>
        public void UnSubscribe(string topic)
        {
            try
            {
                lock (_subscribes)
                {
                    if (_subscribes.ContainsKey(topic))
                        _subscribes.Remove(topic);
                }

                //向服务器发送取消订阅的通知
                var pack = PackageCreater.PackUnSubscribe(ref _byteAuth, topic);

                //这里用同步发送，确保服务端收到数据后再执行后面的逻辑
                _connect.Client.Send(pack, true);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 订阅数据接收回调
        /// </summary>
        /// <param name="simClient"></param>
        /// <param name="frame"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void OnSubscribeReceived(SimClient simClient, FrameInfo frame)
        {
            try
            {
                if (frame.Frame[16] == 0x00)
                {
                    //如果服务端没报错
                    byte[] command = frame.Frame.ReadBytes(17, 2);

                    //如果收到的是服务器返回的订阅结果，直接return
                    if (command[0] == 0xff && command[1] == 0x00)
                        return;

                    //推送主题
                    ushort topicLength = BitConverter.ToUInt16(frame.Frame.ReadBytes(19, 2));
                    string topic = Encoding.UTF8.GetString(frame.Frame.ReadBytes(21, topicLength));
                    int index = 21 + topicLength;

                    //接收主题
                    topicLength = BitConverter.ToUInt16(frame.Frame.ReadBytes(index, 2));
                    index += 2;
                    string stopic = Encoding.UTF8.GetString(frame.Frame.ReadBytes(index, topicLength));
                    index += topicLength;

                    //clientId
                    ushort cLength = BitConverter.ToUInt16(frame.Frame.ReadBytes(index, 2));
                    index += 2;
                    byte[] clientId = frame.Frame.ReadBytes(index, cLength);
                    index += cLength;

                    //内容
                    byte[] msg = frame.Frame.ReadBytes(index);

                    if (_subscribes.ContainsKey(stopic))
                    {
                        _subscribes[stopic]?.Invoke(new SubscribeMessage()
                        {
                            ClientId = clientId,
                            SubscribeTopic = stopic,
                            PublishTopic = topic,
                            Message = msg
                        });
                    }
                }
            }
            catch { }
        }
        #endregion

        /// <summary>
        /// 获取服务端版本号
        /// </summary>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public string GetServerVer()
        {
            string res = string.Empty;
            Connect connect = null;
            try
            {
                byte[] requestId = null;
                var pack = PackageCreater.PackServerVer(ref _byteAuth, DbIndex, ref requestId);

                connect = ConnectPool.GetFreeConnect(IpAddress, Port);
                CommandExecuter command = new CommandExecuter();
                CommandResponse response = command.Execute(connect.Client, pack, Timeout);
                if (response.Data == null)
                    return res;

                res = Encoding.UTF8.GetString(response.Data);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                ConnectPool.FreeConnect(connect);
            }
            return res;
        }

        /// <summary>
        /// 释放连接
        /// </summary>
        public void Dispose()
        {
            try
            {
                if (_connect != null)
                    ConnectPool.FreeConnect(_connect);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}
